/*
 * ginfuncs.c
 *		检查 GIN 索引内容的函数
 *
 * Copyright (c) 2014-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		contrib/pageinspect/ginfuncs.c
 */
#include "postgres.h"

#include "access/gin.h"
#include "access/gin_private.h"
#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "pageinspect.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/rel.h"

#define DatumGetItemPointer(X)	 ((ItemPointer) DatumGetPointer(X))
#define ItemPointerGetDatum(X)	 PointerGetDatum(X)


PG_FUNCTION_INFO_V1(gin_metapage_info);
PG_FUNCTION_INFO_V1(gin_page_opaque_info);
PG_FUNCTION_INFO_V1(gin_leafpage_items);


Datum gin_metapage_info(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	TupleDesc	fc_tupdesc;
	Page		fc_page;
	GinPageOpaque fc_opaq;
	GinMetaPageData *fc_metadata;
	HeapTuple	fc_resultTuple;
	Datum		fc_values[10];
	bool		fc_nulls[10];

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	fc_page = get_page_from_raw(fc_raw_page);

	if (PageIsNew(fc_page))
		PG_RETURN_NULL();

	if (PageGetSpecialSize(fc_page) != MAXALIGN(sizeof(GinPageOpaqueData)))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("input page is not a valid GIN metapage"),
				 errdetail("Expected special size %d, got %d.",
						   (int) MAXALIGN(sizeof(GinPageOpaqueData)),
						   (int) PageGetSpecialSize(fc_page))));

	fc_opaq = GinPageGetOpaque(fc_page);

	if (fc_opaq->flags != GIN_META)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("input page is not a GIN metapage"),
				 errdetail("Flags %04X, expected %04X",
						   fc_opaq->flags, GIN_META)));

	/* 为我们的结果类型构建一个元组描述符 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	fc_metadata = GinPageGetMeta(fc_page);

	memset(fc_nulls, 0, sizeof(fc_nulls));

	fc_values[0] = Int64GetDatum(fc_metadata->head);
	fc_values[1] = Int64GetDatum(fc_metadata->tail);
	fc_values[2] = Int32GetDatum(fc_metadata->tailFreeSize);
	fc_values[3] = Int64GetDatum(fc_metadata->nPendingPages);
	fc_values[4] = Int64GetDatum(fc_metadata->nPendingHeapTuples);

	/* 统计信息，由 VACUUM 更新 */
	fc_values[5] = Int64GetDatum(fc_metadata->nTotalPages);
	fc_values[6] = Int64GetDatum(fc_metadata->nEntryPages);
	fc_values[7] = Int64GetDatum(fc_metadata->nDataPages);
	fc_values[8] = Int64GetDatum(fc_metadata->nEntries);

	fc_values[9] = Int32GetDatum(fc_metadata->ginVersion);

	/* 构建并返回结果元组。 */
	fc_resultTuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	return HeapTupleGetDatum(fc_resultTuple);
}


Datum gin_page_opaque_info(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	TupleDesc	fc_tupdesc;
	Page		fc_page;
	GinPageOpaque fc_opaq;
	HeapTuple	fc_resultTuple;
	Datum		fc_values[3];
	bool		fc_nulls[3];
	Datum		fc_flags[16];
	int			fc_nflags = 0;
	uint16		fc_flagbits;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	fc_page = get_page_from_raw(fc_raw_page);

	if (PageIsNew(fc_page))
		PG_RETURN_NULL();

	if (PageGetSpecialSize(fc_page) != MAXALIGN(sizeof(GinPageOpaqueData)))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("input page is not a valid GIN data leaf page"),
				 errdetail("Expected special size %d, got %d.",
						   (int) MAXALIGN(sizeof(GinPageOpaqueData)),
						   (int) PageGetSpecialSize(fc_page))));

	fc_opaq = GinPageGetOpaque(fc_page);

	/* 为我们的结果类型构建一个元组描述符 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	/* 将标志位掩码转换为一组可读名称 */
	fc_flagbits = fc_opaq->flags;
	if (fc_flagbits & GIN_DATA)
		fc_flags[fc_nflags++] = CStringGetTextDatum("data");
	if (fc_flagbits & GIN_LEAF)
		fc_flags[fc_nflags++] = CStringGetTextDatum("leaf");
	if (fc_flagbits & GIN_DELETED)
		fc_flags[fc_nflags++] = CStringGetTextDatum("deleted");
	if (fc_flagbits & GIN_META)
		fc_flags[fc_nflags++] = CStringGetTextDatum("meta");
	if (fc_flagbits & GIN_LIST)
		fc_flags[fc_nflags++] = CStringGetTextDatum("list");
	if (fc_flagbits & GIN_LIST_FULLROW)
		fc_flags[fc_nflags++] = CStringGetTextDatum("list_fullrow");
	if (fc_flagbits & GIN_INCOMPLETE_SPLIT)
		fc_flags[fc_nflags++] = CStringGetTextDatum("incomplete_split");
	if (fc_flagbits & GIN_COMPRESSED)
		fc_flags[fc_nflags++] = CStringGetTextDatum("compressed");
	fc_flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST |
				  GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED);
	if (fc_flagbits)
	{
		/* 我们不识别的任何标志以十六进制打印 */
		fc_flags[fc_nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(fc_flagbits));
	}

	memset(fc_nulls, 0, sizeof(fc_nulls));

	fc_values[0] = Int64GetDatum(fc_opaq->rightlink);
	fc_values[1] = Int32GetDatum(fc_opaq->maxoff);
	fc_values[2] = PointerGetDatum(construct_array(fc_flags, fc_nflags,
												TEXTOID,
												-1, false, TYPALIGN_INT));

	/* 构建并返回结果元组。 */
	fc_resultTuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	return HeapTupleGetDatum(fc_resultTuple);
}

typedef struct gin_leafpage_items_state
{
	TupleDesc	tupd;
	GinPostingList *seg;
	GinPostingList *lastseg;
} gin_leafpage_items_state;

Datum gin_leafpage_items(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	FuncCallContext *fc_fctx;
	gin_leafpage_items_state *fc_inter_call_data;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	if (SRF_IS_FIRSTCALL())
	{
		TupleDesc	fc_tupdesc;
		MemoryContext fc_mctx;
		Page		fc_page;
		GinPageOpaque fc_opaq;

		fc_fctx = SRF_FIRSTCALL_INIT();
		fc_mctx = MemoryContextSwitchTo(fc_fctx->multi_call_memory_ctx);

		fc_page = get_page_from_raw(fc_raw_page);

		if (PageIsNew(fc_page))
		{
			MemoryContextSwitchTo(fc_mctx);
			PG_RETURN_NULL();
		}

		if (PageGetSpecialSize(fc_page) != MAXALIGN(sizeof(GinPageOpaqueData)))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("input page is not a valid GIN data leaf page"),
					 errdetail("Expected special size %d, got %d.",
							   (int) MAXALIGN(sizeof(GinPageOpaqueData)),
							   (int) PageGetSpecialSize(fc_page))));

		fc_opaq = GinPageGetOpaque(fc_page);
		if (fc_opaq->flags != (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("input page is not a compressed GIN data leaf page"),
					 errdetail("Flags %04X, expected %04X",
							   fc_opaq->flags,
							   (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))));

		fc_inter_call_data = palloc(sizeof(gin_leafpage_items_state));

		/* 为我们的结果类型构建一个元组描述符 */
		if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
			elog(ERROR, "return type must be a row type");

		fc_inter_call_data->tupd = fc_tupdesc;

		fc_inter_call_data->seg = GinDataLeafPageGetPostingList(fc_page);
		fc_inter_call_data->lastseg = (GinPostingList *)
			(((char *) fc_inter_call_data->seg) +
			 GinDataLeafPageGetPostingListSize(fc_page));

		fc_fctx->user_fctx = fc_inter_call_data;

		MemoryContextSwitchTo(fc_mctx);
	}

	fc_fctx = SRF_PERCALL_SETUP();
	fc_inter_call_data = fc_fctx->user_fctx;

	if (fc_inter_call_data->seg != fc_inter_call_data->lastseg)
	{
		GinPostingList *fc_cur = fc_inter_call_data->seg;
		HeapTuple	fc_resultTuple;
		Datum		fc_result;
		Datum		fc_values[3];
		bool		fc_nulls[3];
		int			fc_ndecoded,
					fc_i;
		ItemPointer fc_tids;
		Datum	   *fc_tids_datum;

		memset(fc_nulls, 0, sizeof(fc_nulls));

		fc_values[0] = ItemPointerGetDatum(&fc_cur->first);
		fc_values[1] = UInt16GetDatum(fc_cur->nbytes);

		/* 构建解码项指针的数组 */
		fc_tids = ginPostingListDecode(fc_cur, &fc_ndecoded);
		fc_tids_datum = (Datum *) palloc(fc_ndecoded * sizeof(Datum));
		for (fc_i = 0; fc_i < fc_ndecoded; fc_i++)
			fc_tids_datum[fc_i] = ItemPointerGetDatum(&fc_tids[fc_i]);
		fc_values[2] = PointerGetDatum(construct_array(fc_tids_datum,
													fc_ndecoded,
													TIDOID,
													sizeof(ItemPointerData),
													false, TYPALIGN_SHORT));
		pfree(fc_tids_datum);
		pfree(fc_tids);

		/* 构建并返回结果元组。 */
		fc_resultTuple = heap_form_tuple(fc_inter_call_data->tupd, fc_values, fc_nulls);
		fc_result = HeapTupleGetDatum(fc_resultTuple);

		fc_inter_call_data->seg = GinNextPostingListSegment(fc_cur);

		SRF_RETURN_NEXT(fc_fctx, fc_result);
	}

	SRF_RETURN_DONE(fc_fctx);
}
